﻿@using System.Net
@using System.Text.Json
@using AliasVault.Shared.Models.Spamok
@using AliasVault.Shared.Models.WebApi
@using AliasVault.Client.Main.Services
@inherits ComponentBase
@inject IHttpClientFactory HttpClientFactory
@inject HttpClient HttpClient
@inject JsInteropService JsInteropService
@inject DbService DbService
@inject EmailService EmailService
@using AliasVault.Shared.Core
@inject ILogger<RecentEmails> Logger
@inject MinDurationLoadingService LoadingService
@inject IStringLocalizerFactory LocalizerFactory
@implements IAsyncDisposable
@using Microsoft.Extensions.Localization

@if (EmailModalVisible)
{
    <EmailModal Email="@Email" IsSpamOk="@IsSpamOk" OnClose="CloseEmailModal" OnEmailDeleted="ManualRefresh" />
}

@if (ShowComponent)
{
    <div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
        <div class="flex justify-between">
            <div>
                <h3 class="mb-4 text-xl font-semibold dark:text-white">@Localizer["EmailSectionTitle"]</h3>
            </div>
            <div class="flex justify-end items-center space-x-2">
                @if (DbService.Settings.AutoEmailRefresh)
                {
                    <div class="w-3 h-3 mr-2 rounded-full bg-primary-300 border-2 border-primary-100 animate-pulse" title="@Localizer["AutoRefreshEnabledTooltip"]"></div>
                }
                <button id="recent-email-refresh" @onclick="ManualRefresh" type="button" class="text-blue-700 border border-blue-700 hover:bg-blue-700 hover:text-white focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-full text-sm p-2 text-center inline-flex items-center dark:border-blue-500 dark:text-blue-500 dark:hover:text-white dark:focus:ring-blue-800 dark:hover:bg-blue-500">
                    <svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor">
                        <path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" />
                    </svg>
                </button>
            </div>
        </div>

        @if (IsLoading)
        {
            <div class="flex flex-col mt-6">
                <div class="overflow-x-auto rounded-lg">
                    <div class="inline-block min-w-full align-middle">
                        <div class="overflow-hidden shadow sm:rounded-lg">
                            <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-600">
                                <thead class="bg-gray-50 dark:bg-gray-700">
                                    <tr>
                                        <th scope="col" class="p-4 text-xs font-medium tracking-wider text-left text-gray-500 uppercase dark:text-white">
                                            @Localizer["SubjectColumn"]
                                        </th>
                                        <th scope="col" class="p-4 text-xs font-medium tracking-wider text-left text-gray-500 uppercase dark:text-white">
                                            @Localizer["DateColumn"]
                                        </th>
                                    </tr>
                                </thead>
                                <tbody class="bg-white dark:bg-gray-800">
                                    @for (int i = 0; i < 2; i++)
                                    {
                                        <tr class="hover:bg-gray-50 dark:hover:bg-gray-600">
                                            <td class="p-4">
                                                <SkeletonBase Height="20" AdditionalClasses="w-48">
                                                    <div class="w-full h-full bg-gray-300 dark:bg-gray-700 rounded"></div>
                                                </SkeletonBase>
                                            </td>
                                            <td class="p-4">
                                                <SkeletonBase Height="20" AdditionalClasses="w-24">
                                                    <div class="w-full h-full bg-gray-300 dark:bg-gray-700 rounded"></div>
                                                </SkeletonBase>
                                            </td>
                                        </tr>
                                    }
                                </tbody>
                            </table>
                        </div>
                    </div>
                </div>
            </div>
        }
        else if (!string.IsNullOrEmpty(Error))
        {
            <AlertMessageError Message="@Error" />
        }
        else if (MailboxEmails.Count == 0)
        {
            <div class="text-gray-500 dark:text-gray-400">@Localizer["NoEmailsReceivedMessage"]</div>
        }
        else
        {
            <div class="flex flex-col mt-6">
                <div class="overflow-x-auto rounded-lg">
                    <div class="inline-block min-w-full align-middle">
                        <div class="overflow-hidden shadow sm:rounded-lg">
                            <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-600">
                                <thead class="bg-gray-50 dark:bg-gray-700">
                                    <tr>
                                        <th scope="col" class="p-4 text-xs font-medium tracking-wider text-left text-gray-500 uppercase dark:text-white">
                                            @Localizer["SubjectColumn"]
                                        </th>
                                        <th scope="col" class="p-4 text-xs font-medium tracking-wider text-left text-gray-500 uppercase dark:text-white">
                                            @Localizer["DateColumn"]
                                        </th>
                                    </tr>
                                </thead>
                                <tbody class="bg-white dark:bg-gray-800">
                                    @foreach (var mail in DisplayedEmails)
                                    {
                                        <tr class="hover:bg-gray-50 dark:hover:bg-gray-600 cursor-pointer" @onclick="() => OpenEmail(mail.Id)">
                                            <td class="p-4 text-sm font-normal text-gray-900 whitespace-nowrap dark:text-white">
                                                @if (mail.Subject.Length > 30)
                                                {
                                                    <span>@(mail.Subject.Substring(0, 30))...</span>
                                                }
                                                else
                                                {
                                                    <span>@mail.Subject</span>
                                                }
                                            </td>
                                            <td class="p-4 text-sm font-normal text-gray-500 whitespace-nowrap dark:text-gray-400">
                                                <span>@mail.DateSystem.ToString("yyyy-MM-dd")</span>
                                            </td>
                                        </tr>
                                    }
                                </tbody>
                            </table>
                        </div>
                    </div>
                </div>

                @if (CanLoadMore)
                {
                    <button @onclick="LoadMoreEmails" type="button" class="w-full mt-3 py-1 px-3 bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-md transition-colors duration-200 text-sm text-gray-600 dark:text-gray-300 hover:text-gray-800 dark:hover:text-gray-100 flex items-center justify-center gap-1">
                        <span>@Localizer["LoadMoreButton"]</span>
                        <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
                        </svg>
                    </button>
                }
            </div>
        }
    </div>
}

@code {
    /// <summary>
    /// The email address to show recent emails for.
    /// </summary>
    [Parameter]
    public string? EmailAddress { get; set; } = string.Empty;

    private List<MailboxEmailApiModel> MailboxEmails { get; set; } = new();
    private List<MailboxEmailApiModel> DisplayedEmails { get; set; } = new();
    private bool ShowComponent { get; set; } = false;
    private EmailApiModel Email { get; set; } = new();
    private bool EmailModalVisible { get; set; }
    private string Error { get; set; } = string.Empty;
    private bool IsLoading => LoadingService.IsLoading("recentemails");
    private bool IsSpamOk { get; set; } = false;
    private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Email.RecentEmails", "AliasVault.Client");

    private int DisplayedEmailsCount { get; set; } = 2;
    private const int EmailsPerLoad = 5;
    private bool CanLoadMore => DisplayedEmailsCount < MailboxEmails.Count;

    private const int ACTIVE_TAB_REFRESH_INTERVAL = 2000; // 2 seconds
    private CancellationTokenSource? _pollingCts;
    private DotNetObjectReference<RecentEmails>? _dotNetRef;
    private bool _isPageVisible = true;

    /// <summary>
    /// Callback invoked by JavaScript when the page visibility changes. This is used to start/stop the polling for new emails.
    /// </summary>
    /// <param name="isVisible">Indicates whether the page is visible or not.</param>
    [JSInvokable]
    public void OnVisibilityChange(bool isVisible)
    {
        _isPageVisible = isVisible;

        if (isVisible && DbService.Settings.AutoEmailRefresh)
        {
            // Start polling if visible and auto-refresh is enabled
            StartPolling();
        }
        else
        {
            // Stop polling if hidden
            StopPolling();
        }

        // If becoming visible, do an immediate refresh
        if (isVisible)
        {
            _ = ManualRefresh();
        }
    }

    private void StartPolling()
    {
        // If already polling, no need to start again
        if (_pollingCts != null) {
            return;
        }

        _pollingCts = new CancellationTokenSource();

        // Start polling task
        _ = PollForEmails(_pollingCts.Token);
    }

    private void StopPolling()
    {
        if (_pollingCts != null)
        {
            _pollingCts.Cancel();
            _pollingCts.Dispose();
            _pollingCts = null;
        }
    }

    private async Task PollForEmails(CancellationToken cancellationToken)
    {
        try
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                await LoadRecentEmailsAsync();
                await Task.Delay(ACTIVE_TAB_REFRESH_INTERVAL, cancellationToken);
            }
        }
        catch (OperationCanceledException)
        {
            // Normal cancellation, ignore.
        }
        catch (Exception ex)
        {
            Logger.LogError(ex, "Error in email refresh polling");
        }
    }

    /// <inheritdoc />
    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();

        if (EmailAddress is null)
        {
            return;
        }

        // Check if email has a known SpamOK domain, if not, don't show this component.
        ShowComponent = EmailService.IsAliasVaultSupportedDomain(EmailAddress);
        IsSpamOk = EmailService.IsSpamOkDomain(EmailAddress);

        // Create a single object reference for JS interop
        _dotNetRef = DotNetObjectReference.Create(this);
        await JsInteropService.RegisterVisibilityCallback(_dotNetRef);

        // Only start polling if auto-refresh is enabled and page is visible
        if (DbService.Settings.AutoEmailRefresh && _isPageVisible)
        {
            StartPolling();
        }
    }

    /// <inheritdoc />
    public async ValueTask DisposeAsync()
    {
        // Stop polling
        StopPolling();

        // Unregister the visibility callback using the same reference
        if (_dotNetRef != null)
        {
            await JsInteropService.UnregisterVisibilityCallback(_dotNetRef);
            _dotNetRef.Dispose();
        }
    }

    /// <inheritdoc />
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await base.OnAfterRenderAsync(firstRender);

        if (!ShowComponent)
        {
            return;
        }

        if (firstRender)
        {
            await ManualRefresh();
        }
    }

    /// <inheritdoc />
    protected override void OnParametersSet()
    {
        base.OnParametersSet();

        if (EmailAddress is null)
        {
            return;
        }

        IsSpamOk = EmailService.IsSpamOkDomain(EmailAddress);
    }

    /// <summary>
    /// Manually refresh the emails.
    /// </summary>
    /// <returns></returns>
    private async Task ManualRefresh()
    {
        LoadingService.StartLoading("recentemails", 300, StateHasChanged);
        StateHasChanged();
        CloseEmailModal();
        DisplayedEmailsCount = 2; // Reset to initial count
        await LoadRecentEmailsAsync();
        LoadingService.FinishLoading("recentemails", StateHasChanged);
        StateHasChanged();
    }

    /// <summary>
    /// (Re)load recent emails by making an API call to the server.
    /// </summary>
    /// <returns>Task.</returns>
    private async Task LoadRecentEmailsAsync()
    {
        if (!ShowComponent || EmailAddress is null)
        {
            return;
        }

        // Get email prefix, which is the part before the @ symbol.
        string emailPrefix = EmailAddress.Split('@')[0];

        if (EmailService.IsSpamOkDomain(EmailAddress))
        {
            await LoadSpamOkEmails(emailPrefix);
        }
        else if (EmailService.IsAliasVaultDomain(EmailAddress))
        {
            await LoadAliasVaultEmails();
        }

        StateHasChanged();
    }

    /// <summary>
    /// Open the email modal.
    /// </summary>
    private async Task OpenEmail(int emailId)
    {
        if (EmailAddress is null)
        {
            return;
        }

        // Get email prefix, which is the part before the @ symbol.
        string emailPrefix = EmailAddress.Split('@')[0];

        if (EmailService.IsSpamOkDomain(EmailAddress))
        {
            await ShowSpamOkEmailInModal(emailPrefix, emailId);
        }
        else if (EmailService.IsAliasVaultDomain(EmailAddress))
        {
            await ShowAliasVaultEmailInModal(emailId);
        }
    }

    /// <summary>
    /// Load recent emails from SpamOK.
    /// </summary>
    private async Task LoadSpamOkEmails(string emailPrefix)
    {
        // We construct a new HttpClient to avoid using the default one, which is used for the API and sends
        // the Authorization header. We don't want to send the Authorization header to the external email API.
        var client = HttpClientFactory.CreateClient("EmailClient");
        var request = new HttpRequestMessage(HttpMethod.Get, $"https://api.spamok.com/v2/EmailBox/{emailPrefix}");
        request.Headers.Add("X-Asdasd-Platform-Id", "av-web");
        request.Headers.Add("X-Asdasd-Platform-Version", AppInfo.GetFullVersion());

        var response = await client.SendAsync(request);
        if (response.IsSuccessStatusCode)
        {
            var mailbox = await response.Content.ReadFromJsonAsync<MailboxApiModel>();
            if (mailbox != null)
            {
                MailboxEmails = mailbox.Mails.ToList();
                UpdateDisplayedEmails();
            }
        }
    }

    /// <summary>
    /// Load recent emails from SpamOK.
    /// </summary>
    private async Task ShowSpamOkEmailInModal(string emailPrefix, int emailId)
    {
        var client = HttpClientFactory.CreateClient("EmailClient");
        var request = new HttpRequestMessage(HttpMethod.Get, $"https://api.spamok.com/v2/Email/{emailPrefix}/{emailId}");
        request.Headers.Add("X-Asdasd-Platform-Id", "av-web");
        request.Headers.Add("X-Asdasd-Platform-Version", AppInfo.GetFullVersion());

        var response = await client.SendAsync(request);
        if (response.IsSuccessStatusCode)
        {
            var mail = await response.Content.ReadFromJsonAsync<EmailApiModel>();
            if (mail != null)
            {
                Email = mail;
                EmailModalVisible = true;
                StateHasChanged();
            }
        }
    }

    /// <summary>
    /// Load recent emails from AliasVault.
    /// </summary>
    private async Task LoadAliasVaultEmails()
    {
        var request = new HttpRequestMessage(HttpMethod.Get, $"v1/EmailBox/{EmailAddress}");
        try
        {
            var response = await HttpClient.SendAsync(request);
            if (response.IsSuccessStatusCode)
            {
                var mailbox = await response.Content.ReadFromJsonAsync<MailboxApiModel>();
                await UpdateMailboxEmails(mailbox);
            }
            else
            {
                var errorContent = await response.Content.ReadAsStringAsync();
                var errorResponse = JsonSerializer.Deserialize<ApiErrorResponse>(errorContent);
                switch (response.StatusCode)
                {
                    case HttpStatusCode.BadRequest:
                        if (errorResponse != null)
                        {
                            switch (errorResponse.Code)
                            {
                                case "CLAIM_DOES_NOT_MATCH_USER":
                                    Error = Localizer["EmailAddressInUseError"];
                                    break;
                                case "CLAIM_DOES_NOT_EXIST":
                                    Error = Localizer["EmailLoadError"];
                                    break;
                                default:
                                    throw new ArgumentException(errorResponse.Message);
                            }
                        }

                        break;
                    case HttpStatusCode.Unauthorized:
                        throw new UnauthorizedAccessException(errorResponse?.Message);
                    default:
                        throw new WebException(errorResponse?.Message);
                }
            }
        }
        catch (Exception ex)
        {
            Error = ex.Message;
            Logger.LogError(ex, "An error occurred while loading AliasVault emails.");
        }
    }

    /// <summary>
    /// Update the mailbox emails and decrypt the subject locally.
    /// </summary>
    private async Task UpdateMailboxEmails(MailboxApiModel? mailbox)
    {
        if (mailbox?.Mails != null)
        {
            MailboxEmails = mailbox.Mails.ToList();
        }

        MailboxEmails = await EmailService.DecryptEmailList(MailboxEmails);
        Error = string.Empty;
        UpdateDisplayedEmails();
    }

    /// <summary>
    /// Load more emails to display.
    /// </summary>
    private void LoadMoreEmails()
    {
        DisplayedEmailsCount = Math.Min(DisplayedEmailsCount + EmailsPerLoad, MailboxEmails.Count);
        UpdateDisplayedEmails();
    }

    /// <summary>
    /// Update the displayed emails based on the current count.
    /// </summary>
    private void UpdateDisplayedEmails()
    {
        DisplayedEmails = MailboxEmails.Take(DisplayedEmailsCount).ToList();
        StateHasChanged();
    }

    /// <summary>
    /// Load recent emails from AliasVault.
    /// </summary>
    private async Task ShowAliasVaultEmailInModal(int emailId)
    {
        EmailApiModel? mail = await HttpClient.GetFromJsonAsync<EmailApiModel>($"v1/Email/{emailId}");
        if (mail != null)
        {
            // Decrypt the email content locally.
            var context = await DbService.GetDbContextAsync();
            var privateKey = await context.EncryptionKeys.FirstOrDefaultAsync(x => x.PublicKey == mail.EncryptionKey);
            if (privateKey is not null)
            {
                mail = await EmailService.DecryptEmail(mail);
            }

            Email = mail;
            EmailModalVisible = true;
            StateHasChanged();
        }
    }

    /// <summary>
    /// Close the email modal.
    /// </summary>
    private void CloseEmailModal()
    {
        EmailModalVisible = false;
        StateHasChanged();
    }
}
